/*
 * The Open Source Unix Installer
 *
 * The MIT License
 *
 * Copyright 2011-2012 Andrey Pudov.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*
 * @author  Andrey Pudov    <andrey@andreypudov.com>
 * @version 0.00.00
 * @name    shell.c
 * @date:   Feb 22, 2013
 */

#include "shell.h"

/* private code declaration */
const int AUTH_SUCCESS = 0;
const int AUTH_FAILURE = 1;

/* public code definition */
Shell* installer_shell_new() {
    Logger *logger = installer_core_get_logger();
    
    Shell *shell = (Shell*) malloc(sizeof(Shell));
    if (shell == NULL) {
        installer_logger_log(logger, SEVERE, "shell", "Can't create new shell");
        exit(EXIT_FAILURE);
    }
    
    shell->pty = 0;
    
    return shell;
}

void installer_shell_delete(Shell *shell) {
    if (shell != NULL) {
        free(shell);
    }
}

int installer_shell_execute(Shell *shell, const char *login, 
                             const char *password, const char *command) {
	Logger *logger = installer_core_get_logger();

	char  buffer[128] = {0};

	/* p - do not reset environment variables */
	/* c - pass a single command to the shell */
	const char *command_list[] = {"/bin/su", login, "-p", "-c", command, (char*) NULL};

	int   status = 1;
	pid_t cpid   = 0;

	fd_set set;

	const struct timespec limit = {.tv_sec = 0, .tv_nsec = 10000};

	if ((shell == NULL) || (login == NULL) || (password == NULL) 
            || (command == NULL)) {
		installer_logger_log(logger, SEVERE, "shell", "Invalid arguments exception");
		return (AUTH_FAILURE);
	}

	/* create new terminal */
	if ((cpid = forkpty(&(shell->pty), NULL, NULL, NULL)) < 0) {
		installer_logger_log(logger, SEVERE, "shell", "Can't create new tty");
		return (AUTH_FAILURE);
	}

	if (cpid == 0) {
		/* run a program in a new session */
		execv(command_list[0], (char**) command_list);

		installer_logger_log(logger, SEVERE, "shell", "Can't create new session");
		return (AUTH_FAILURE);
	}

	for (int index = 16; index && status; --index) {
		FD_ZERO(&set);
		FD_SET(shell->pty, &set);

		/* select ready descriptor */
		if (pselect(shell->pty + 1, &set, NULL, NULL, &limit, NULL) < 0) {
			installer_logger_log(logger, SEVERE, "shell", "Can't select descriptor");
			exit(EXIT_FAILURE);
		}

		if (FD_ISSET(shell->pty, &set)) {
			read(shell->pty, buffer, sizeof(buffer) - 1);
			status = 0;
		}

		/* sleep a specified number of microsecondses */
		usleep(1000);
	}

	/* check if timout is riched */
	if (status) {
		kill(cpid, SIGKILL);

		installer_logger_log(logger, SEVERE, "shell", "Login shell is not riched");
		return (AUTH_FAILURE);
	}

	/* pass the password */
	snprintf(buffer, 64, "%s\n", password);
	write(shell->pty, buffer, strlen(buffer));

   	while (!waitpid(cpid, &status, WNOHANG)) {
		FD_ZERO(&set);
		FD_SET(shell->pty, &set);
		FD_SET(STDIN_FILENO, &set);

		if (pselect(((shell->pty > STDIN_FILENO) ? shell->pty : STDIN_FILENO) + 1,
					&set, NULL, NULL, &limit, NULL) < 0) {
			installer_logger_log(logger, SEVERE, "shell", "Can't select terminal description");
			return (AUTH_FAILURE);
		}

		if (FD_ISSET(shell->pty, &set)) {
			status = read(shell->pty, buffer, sizeof(buffer) - 1);
			write(STDOUT_FILENO, buffer, status);
		} else if (FD_ISSET(STDIN_FILENO, &set)) {
			status = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
			write(shell->pty, buffer, status);
		}

		usleep(100);
	}

	/* send EOF to the child process */
	close(shell->pty);

	/* exit status of a child process */
	if (!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
		sprintf(buffer, "Child process exited abnormally (exit code = %d)", status);
		installer_logger_log(logger, SEVERE, "shell", buffer);

		return (AUTH_FAILURE);
	}

	return (AUTH_SUCCESS);
}
